/**
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @flow
 * @format
 * @oncall relay
 */

'use strict';
import type {NormalizationSplitOperation} from '../../util/NormalizationNode';
import type {NormalizationRootNode} from '../../util/NormalizationNode';
import type {LogEvent, Snapshot} from '../RelayStoreTypes';
import type {RequestParameters} from 'relay-runtime/util/RelayConcreteNode';
import type {
  CacheConfig,
  Variables,
} from 'relay-runtime/util/RelayRuntimeTypes';

const {
  MultiActorEnvironment,
  getActorIdentifier,
} = require('../../multi-actor-environment');
const RelayNetwork = require('../../network/RelayNetwork');
const RelayObservable = require('../../network/RelayObservable');
const {graphql} = require('../../query/GraphQLTag');
const RelayFeatureFlags = require('../../util/RelayFeatureFlags');
const RelayModernEnvironment = require('../RelayModernEnvironment');
const {
  createOperationDescriptor,
} = require('../RelayModernOperationDescriptor');
const {getSingularSelector} = require('../RelayModernSelector');
const RelayModernStore = require('../RelayModernStore');
const RelayRecordSource = require('../RelayRecordSource');
const nullthrows = require('nullthrows');
const {
  disallowWarnings,
  injectPromisePolyfill__DEPRECATED,
} = require('relay-test-utils-internal');

injectPromisePolyfill__DEPRECATED();
disallowWarnings();

function runWithFeatureFlags(setFlags: (typeof RelayFeatureFlags) => void) {
  describe.each(['RelayModernEnvironment', 'MultiActorEnvironment'])(
    'execute() a query with nested @match',
    environmentType => {
      describe(environmentType, () => {
        let callbacks: {
          +complete: JestMockFn<$ReadOnlyArray<mixed>, mixed>,
          +error: JestMockFn<$ReadOnlyArray<Error>, mixed>,
          +next: JestMockFn<$ReadOnlyArray<mixed>, mixed>,
          +start?: JestMockFn<$ReadOnlyArray<mixed>, mixed>,
          +unsubscribe?: JestMockFn<$ReadOnlyArray<mixed>, mixed>,
        };
        let complete;
        let dataSource;
        let environment;
        let error;
        let fetch;
        let markdownRendererFragment;
        let markdownRendererNormalizationFragment;
        let next;
        let operation;
        let operationCallback;
        let operationLoader: {
          get: (reference: mixed) => ?NormalizationRootNode,
          load: JestMockFn<
            $ReadOnlyArray<mixed>,
            Promise<?NormalizationRootNode>,
          >,
        };
        let plaintextRendererFragment;
        let plaintextRendererNormalizationFragment;
        let query;
        let source;
        let store;
        let variables;
        let logger;
        let resolveFragments;

        beforeEach(() => {
          resolveFragments = [] as Array<
            | any
            | ((
                result?:
                  | Promise<NormalizationSplitOperation>
                  | NormalizationSplitOperation,
              ) => void),
          >;
          setFlags(RelayFeatureFlags);
          markdownRendererNormalizationFragment = require('./__generated__/RelayModernEnvironmentExecuteWithSiblingAndNestedModuleTestMarkdownUserNameRenderer_name$normalization.graphql');
          plaintextRendererNormalizationFragment = require('./__generated__/RelayModernEnvironmentExecuteWithSiblingAndNestedModuleTestPlainUserNameRenderer_name$normalization.graphql');

          query = graphql`
            query RelayModernEnvironmentExecuteWithSiblingAndNestedModuleTestUserQuery(
              $id: ID!
            ) {
              node(id: $id) {
                ... on User {
                  outerRendererA: nameRenderer
                    @match(
                      key: "RelayModernEnvironmentExecuteWithSiblingAndNestedModuleTestUserQuery_rendererA"
                    ) {
                    ...RelayModernEnvironmentExecuteWithSiblingAndNestedModuleTestMarkdownUserNameRenderer_name
                      @module(name: "MarkdownUserNameRenderer.react")
                  }
                  outerRendererB: nameRenderer
                    @match(
                      key: "RelayModernEnvironmentExecuteWithSiblingAndNestedModuleTestUserQuery_rendererB"
                    ) {
                    ...RelayModernEnvironmentExecuteWithSiblingAndNestedModuleTestPlainUserNameRenderer_name
                      @module(name: "PlainTextUserNameRenderer.react")
                  }
                }
              }
            }
          `;

          markdownRendererFragment = graphql`
            fragment RelayModernEnvironmentExecuteWithSiblingAndNestedModuleTestMarkdownUserNameRenderer_name on MarkdownUserNameRenderer {
              __typename
              markdown
              user {
                name
                innerRenderer: nameRenderer {
                  ...RelayModernEnvironmentExecuteWithSiblingAndNestedModuleTestPlainUserNameRenderer_name
                    @module(name: "PlainUserNameRenderer.react")
                }
              }
            }
          `;

          plaintextRendererFragment = graphql`
            fragment RelayModernEnvironmentExecuteWithSiblingAndNestedModuleTestPlainUserNameRenderer_name on PlainUserNameRenderer {
              data {
                text
              }
              user {
                name
              }
            }
          `;
          variables = {id: '1'};
          operation = createOperationDescriptor(query, variables);

          complete = jest.fn<$ReadOnlyArray<mixed>, mixed>();
          error = jest.fn<$ReadOnlyArray<Error>, mixed>();
          next = jest.fn<$ReadOnlyArray<mixed>, mixed>();
          callbacks = {complete, error, next};
          fetch = (
            _query: RequestParameters,
            _variables: Variables,
            _cacheConfig: CacheConfig,
          ) => {
            // $FlowFixMe[missing-local-annot] Error found while enabling LTI on this file
            return RelayObservable.create(sink => {
              dataSource = sink;
            });
          };
          operationLoader = {
            get: jest.fn(),
            load: jest.fn(moduleName => {
              return new Promise(resolve => {
                resolveFragments.push(resolve);
              });
            }),
          };
          source = RelayRecordSource.create();
          logger = jest.fn<[LogEvent], void>();
          store = new RelayModernStore(source, {
            log: logger,
          });

          const multiActorEnvironment = new MultiActorEnvironment({
            createNetworkForActor: _actorID => RelayNetwork.create(fetch),
            createStoreForActor: _actorID => store,
            operationLoader,
          });
          environment =
            environmentType === 'MultiActorEnvironment'
              ? multiActorEnvironment.forActor(getActorIdentifier('actor:1234'))
              : new RelayModernEnvironment({
                  network: RelayNetwork.create(fetch),
                  operationLoader,
                  store,
                });

          const operationSnapshot = environment.lookup(operation.fragment);
          operationCallback = jest.fn<[Snapshot], void>();
          environment.subscribe(operationSnapshot, operationCallback);
        });

        it('resolves sibling modules in one tick, and notifies once if the feature flag is on', () => {
          environment.execute({operation}).subscribe(callbacks);
          const payload = {
            data: {
              node: {
                __typename: 'User',
                id: '1',
                outerRendererA: {
                  __module_component_RelayModernEnvironmentExecuteWithSiblingAndNestedModuleTestUserQuery_rendererA:
                    'MarkdownUserNameRenderer.react',
                  __module_operation_RelayModernEnvironmentExecuteWithSiblingAndNestedModuleTestUserQuery_rendererA:
                    'RelayModernEnvironmentExecuteWithSiblingAndNestedModuleTestMarkdownUserNameRenderer_name$normalization.graphql',
                  __typename: 'MarkdownUserNameRenderer',
                  markdown: 'markdown payload',
                  user: {
                    id: '2',
                    innerRenderer: null,
                    name: 'Mark',
                  },
                },
                outerRendererB: {
                  __module_component_RelayModernEnvironmentExecuteWithSiblingAndNestedModuleTestUserQuery_rendererB:
                    'PlainUserNameRenderer.react',
                  __module_operation_RelayModernEnvironmentExecuteWithSiblingAndNestedModuleTestUserQuery_rendererB:
                    'RelayModernEnvironmentExecuteWithSiblingAndNestedModuleTestPlainUserNameRenderer_name$normalization.graphql',
                  __typename: 'PlainUserNameRenderer',
                  data: {
                    id: 'data-2',
                    text: 'plaintext!',
                  },
                  user: {
                    id: '2',
                    name: 'Zuck',
                  },
                },
              },
            },
          };
          dataSource.next(payload);
          jest.runAllTimers();
          next.mockClear();

          expect(operationLoader.load).toBeCalledTimes(2);
          expect(operationLoader.load.mock.calls[0][0]).toEqual(
            'RelayModernEnvironmentExecuteWithSiblingAndNestedModuleTestMarkdownUserNameRenderer_name$normalization.graphql',
          );
          expect(operationLoader.load.mock.calls[1][0]).toEqual(
            'RelayModernEnvironmentExecuteWithSiblingAndNestedModuleTestPlainUserNameRenderer_name$normalization.graphql',
          );

          expect(operationCallback).toBeCalledTimes(1);
          const operationSnapshot = operationCallback.mock.calls[0][0];
          const outerRendererASelector = nullthrows(
            getSingularSelector(
              markdownRendererFragment,
              (operationSnapshot.data?.node as any)?.outerRendererA,
            ),
          );
          const outerRendererASnapshot = environment.lookup(
            outerRendererASelector,
          );
          expect(outerRendererASnapshot.isMissingData).toBe(true);
          const outerRendererACallback = jest.fn<[Snapshot], void>();
          environment.subscribe(outerRendererASnapshot, outerRendererACallback);

          const outerRendererBSelector = nullthrows(
            getSingularSelector(
              plaintextRendererFragment,
              (operationSnapshot.data?.node as any)?.outerRendererB,
            ),
          );
          const outerRendererBSnapshot = environment.lookup(
            outerRendererBSelector,
          );
          expect(outerRendererBSnapshot.isMissingData).toBe(true);
          const outerRendererBCallback = jest.fn<[Snapshot], void>();
          environment.subscribe(outerRendererBSnapshot, outerRendererBCallback);

          logger.mockClear();
          resolveFragments[0](markdownRendererNormalizationFragment);
          resolveFragments[1](plaintextRendererNormalizationFragment);
          jest.runAllTimers();

          expect(operationCallback).toBeCalledTimes(1);
          expect(next).toBeCalledTimes(0);

          expect(outerRendererBCallback).toBeCalledTimes(1);
          const nextOuterRendererBSnapshot =
            outerRendererBCallback.mock.calls[0][0];
          expect(nextOuterRendererBSnapshot.isMissingData).toBe(false);
          expect(nextOuterRendererBSnapshot.data).toEqual({
            data: {
              text: 'plaintext!',
            },
            user: {
              name: 'Zuck',
            },
          });
          let nextOuterRendererASnapshot;
          // The store is notified once with the batching on
          if (RelayFeatureFlags.BATCH_ASYNC_MODULE_UPDATES_FN != null) {
            expect(outerRendererACallback).toBeCalledTimes(1);
            nextOuterRendererASnapshot =
              outerRendererACallback.mock.calls[0][0];
          } else {
            expect(outerRendererACallback).toBeCalledTimes(2);
            expect(outerRendererACallback.mock.calls[0][0].isMissingData).toBe(
              false,
            );
            expect(outerRendererACallback.mock.calls[0][0].data).toEqual({
              __typename: 'MarkdownUserNameRenderer',
              markdown: 'markdown payload',
              user: {
                innerRenderer: null,
                name: 'Mark',
              },
            });
            nextOuterRendererASnapshot =
              outerRendererACallback.mock.calls[1][0];
          }
          expect(nextOuterRendererASnapshot.isMissingData).toBe(false);
          expect(nextOuterRendererASnapshot.data).toEqual({
            __typename: 'MarkdownUserNameRenderer',
            markdown: 'markdown payload',
            user: {
              innerRenderer: null,
              name: 'Zuck',
            },
          });
        });

        it('resolves sibling and nested modules in one tick, and notifies once if the feature flag is on', () => {
          environment.execute({operation}).subscribe(callbacks);
          const payload = {
            data: {
              node: {
                __typename: 'User',
                id: '1',
                outerRendererA: {
                  __module_component_RelayModernEnvironmentExecuteWithSiblingAndNestedModuleTestUserQuery_rendererA:
                    'MarkdownUserNameRenderer.react',
                  __module_operation_RelayModernEnvironmentExecuteWithSiblingAndNestedModuleTestUserQuery_rendererA:
                    'RelayModernEnvironmentExecuteWithSiblingAndNestedModuleTestMarkdownUserNameRenderer_name$normalization.graphql',
                  __typename: 'MarkdownUserNameRenderer',
                  markdown: 'markdown payload',
                  user: {
                    id: '2',
                    innerRenderer: {
                      __module_component_RelayModernEnvironmentExecuteWithSiblingAndNestedModuleTestMarkdownUserNameRenderer_name:
                        'PlainUserNameRenderer.react',
                      __module_operation_RelayModernEnvironmentExecuteWithSiblingAndNestedModuleTestMarkdownUserNameRenderer_name:
                        'RelayModernEnvironmentExecuteWithSiblingAndNestedModuleTestPlainUserNameRenderer_name$normalization.graphql',
                      __typename: 'PlainUserNameRenderer',
                      data: {
                        id: 'data-inner',
                        text: 'plaintext-inner!',
                      },
                      user: {
                        id: '2',
                        name: 'innerRenderer',
                      },
                    },
                    name: 'outerRendererA',
                  },
                },
                outerRendererB: {
                  __module_component_RelayModernEnvironmentExecuteWithSiblingAndNestedModuleTestUserQuery_rendererB:
                    'PlainUserNameRenderer.react',
                  __module_operation_RelayModernEnvironmentExecuteWithSiblingAndNestedModuleTestUserQuery_rendererB:
                    'RelayModernEnvironmentExecuteWithSiblingAndNestedModuleTestPlainUserNameRenderer_name$normalization.graphql',
                  __typename: 'PlainUserNameRenderer',
                  data: {
                    id: 'data-2',
                    text: 'plaintext!',
                  },
                  user: {
                    id: '2',
                    name: 'outerRendererB',
                  },
                },
              },
            },
          };
          dataSource.next(payload);
          jest.runAllTimers();
          next.mockClear();

          expect(operationLoader.load).toBeCalledTimes(2);
          expect(operationLoader.load.mock.calls[0][0]).toEqual(
            'RelayModernEnvironmentExecuteWithSiblingAndNestedModuleTestMarkdownUserNameRenderer_name$normalization.graphql',
          );
          expect(operationLoader.load.mock.calls[1][0]).toEqual(
            'RelayModernEnvironmentExecuteWithSiblingAndNestedModuleTestPlainUserNameRenderer_name$normalization.graphql',
          );

          expect(operationCallback).toBeCalledTimes(1);
          const operationSnapshot = operationCallback.mock.calls[0][0];
          const outerRendererASelector = nullthrows(
            getSingularSelector(
              markdownRendererFragment,
              (operationSnapshot.data?.node as any)?.outerRendererA,
            ),
          );
          const outerRendererASnapshot = environment.lookup(
            outerRendererASelector,
          );
          expect(outerRendererASnapshot.isMissingData).toBe(true);
          const outerRendererACallback = jest.fn<[Snapshot], void>();
          environment.subscribe(outerRendererASnapshot, outerRendererACallback);

          const outerRendererBSelector = nullthrows(
            getSingularSelector(
              plaintextRendererFragment,
              (operationSnapshot.data?.node as any)?.outerRendererB,
            ),
          );
          const outerRendererBSnapshot = environment.lookup(
            outerRendererBSelector,
          );
          expect(outerRendererBSnapshot.isMissingData).toBe(true);
          const outerRendererBCallback = jest.fn<[Snapshot], void>();
          environment.subscribe(outerRendererBSnapshot, outerRendererBCallback);

          logger.mockClear();
          resolveFragments[0](markdownRendererNormalizationFragment);
          resolveFragments[1](plaintextRendererNormalizationFragment);
          jest.runAllImmediates();
          resolveFragments[2](plaintextRendererNormalizationFragment);
          jest.runAllTimers();

          expect(operationCallback).toBeCalledTimes(1);
          expect(next).toBeCalledTimes(0);
          let nextOuterRendererASnapshot;
          let nextOuterRendererBSnapshot;
          if (RelayFeatureFlags.BATCH_ASYNC_MODULE_UPDATES_FN != null) {
            expect(outerRendererACallback).toBeCalledTimes(1);
            expect(outerRendererBCallback).toBeCalledTimes(1);
            nextOuterRendererASnapshot =
              outerRendererACallback.mock.calls[0][0];
            nextOuterRendererBSnapshot =
              outerRendererBCallback.mock.calls[0][0];
          } else {
            expect(outerRendererACallback).toBeCalledTimes(3);
            expect(outerRendererBCallback).toBeCalledTimes(2);
            nextOuterRendererASnapshot =
              outerRendererACallback.mock.calls[2][0];
            nextOuterRendererBSnapshot =
              outerRendererBCallback.mock.calls[1][0];
            expect(outerRendererBCallback.mock.calls[0][0].data).toEqual({
              data: {
                text: 'plaintext!',
              },
              user: {
                name: 'outerRendererB',
              },
            });
          }
          expect(nextOuterRendererASnapshot.isMissingData).toBe(false);
          expect(nextOuterRendererASnapshot.data).toEqual({
            __typename: 'MarkdownUserNameRenderer',
            markdown: 'markdown payload',
            user: {
              innerRenderer: {
                __fragmentOwner: operation.request,
                __fragmentPropName: 'name',
                __fragments: {
                  RelayModernEnvironmentExecuteWithSiblingAndNestedModuleTestPlainUserNameRenderer_name:
                    {},
                },
                __id: 'client:2:nameRenderer',
                __module_component: 'PlainUserNameRenderer.react',
              },
              name: 'innerRenderer',
            },
          });
          expect(nextOuterRendererBSnapshot.isMissingData).toBe(false);
          expect(nextOuterRendererBSnapshot.data).toEqual({
            data: {
              text: 'plaintext!',
            },
            user: {
              name: 'innerRenderer',
            },
          });
          const innerSelector = nullthrows(
            getSingularSelector(
              plaintextRendererFragment,
              (nextOuterRendererASnapshot.data?.user as $FlowFixMe)
                ?.innerRenderer,
            ),
          );
          const innerSnapshot = environment.lookup(innerSelector);
          expect(innerSnapshot.isMissingData).toBe(false);
          expect(innerSnapshot.data).toEqual({
            data: {
              text: 'plaintext-inner!',
            },
            user: {
              name: 'innerRenderer',
            },
          });
        });

        it('calls complete only after modules are resolved and published', () => {
          environment.execute({operation}).subscribe(callbacks);
          const payload = {
            data: {
              node: {
                __typename: 'User',
                id: '1',
                outerRendererA: {
                  __module_component_RelayModernEnvironmentExecuteWithSiblingAndNestedModuleTestUserQuery_rendererA:
                    'MarkdownUserNameRenderer.react',
                  __module_operation_RelayModernEnvironmentExecuteWithSiblingAndNestedModuleTestUserQuery_rendererA:
                    'RelayModernEnvironmentExecuteWithSiblingAndNestedModuleTestMarkdownUserNameRenderer_name$normalization.graphql',
                  __typename: 'MarkdownUserNameRenderer',
                  markdown: 'markdown payload',
                  user: {
                    id: '2',
                    innerRenderer: {
                      __typename: 'MarkdownUserNameRenderer',
                    },
                    name: 'outerRendererA',
                  },
                },
                outerRendererB: {
                  __module_component_RelayModernEnvironmentExecuteWithSiblingAndNestedModuleTestUserQuery_rendererB:
                    'PlainUserNameRenderer.react',
                  __module_operation_RelayModernEnvironmentExecuteWithSiblingAndNestedModuleTestUserQuery_rendererB:
                    'RelayModernEnvironmentExecuteWithSiblingAndNestedModuleTestPlainUserNameRenderer_name$normalization.graphql',
                  __typename: 'PlainUserNameRenderer',
                  data: {
                    id: 'data-2',
                    text: 'plaintext!',
                  },
                  user: {
                    id: '2',
                    name: 'outerRendererB',
                  },
                },
              },
            },
          };
          dataSource.next(payload);
          jest.runAllTimers();
          next.mockClear();

          dataSource.complete();
          jest.runAllImmediates();
          expect(next).toBeCalledTimes(0);
          expect(error).toBeCalledTimes(0);
          expect(complete).toBeCalledTimes(0);

          resolveFragments[0](markdownRendererNormalizationFragment);
          resolveFragments[1](plaintextRendererNormalizationFragment);
          if (RelayFeatureFlags.BATCH_ASYNC_MODULE_UPDATES_FN != null) {
            // Resolve fragments but not publish
            jest.runAllImmediates();
            expect(
              environment
                .getOperationTracker()
                .getPendingOperationsAffectingOwner(operation.request),
            ).not.toBe(null);
            expect(complete).toBeCalledTimes(0);
            // Publish to store
            jest.runAllTimers();
            expect(complete).toBeCalledTimes(1);
            expect(
              environment
                .getOperationTracker()
                .getPendingOperationsAffectingOwner(operation.request),
            ).toBe(null);
          } else {
            expect(
              environment
                .getOperationTracker()
                .getPendingOperationsAffectingOwner(operation.request),
            ).not.toBe(null);
            jest.runAllImmediates();
            expect(complete).toBeCalledTimes(1);
            expect(
              environment
                .getOperationTracker()
                .getPendingOperationsAffectingOwner(operation.request),
            ).toBe(null);
          }
        });

        it('cancels @module processing if unsubscribed', () => {
          const subscription = environment
            .execute({operation})
            .subscribe(callbacks);
          const payload = {
            data: {
              node: {
                __typename: 'User',
                id: '1',
                outerRendererA: {
                  __module_component_RelayModernEnvironmentExecuteWithSiblingAndNestedModuleTestUserQuery_rendererA:
                    'MarkdownUserNameRenderer.react',
                  __module_operation_RelayModernEnvironmentExecuteWithSiblingAndNestedModuleTestUserQuery_rendererA:
                    'RelayModernEnvironmentExecuteWithSiblingAndNestedModuleTestMarkdownUserNameRenderer_name$normalization.graphql',
                  __typename: 'MarkdownUserNameRenderer',
                  markdown: 'markdown payload',
                  user: {
                    id: '2',
                    innerRenderer: {
                      __typename: 'MarkdownUserNameRenderer',
                    },
                    name: 'outerRendererA',
                  },
                },
                outerRendererB: {
                  __module_component_RelayModernEnvironmentExecuteWithSiblingAndNestedModuleTestUserQuery_rendererB:
                    'PlainUserNameRenderer.react',
                  __module_operation_RelayModernEnvironmentExecuteWithSiblingAndNestedModuleTestUserQuery_rendererB:
                    'RelayModernEnvironmentExecuteWithSiblingAndNestedModuleTestPlainUserNameRenderer_name$normalization.graphql',
                  __typename: 'PlainUserNameRenderer',
                  data: {
                    id: 'data-2',
                    text: 'plaintext!',
                  },
                  user: {
                    id: '2',
                    name: 'outerRendererB',
                  },
                },
              },
            },
          };
          dataSource.next(payload);
          jest.runAllTimers();
          const operationSnapshot = operationCallback.mock.calls[0][0];
          const outerRendererASelector = nullthrows(
            getSingularSelector(
              markdownRendererFragment,
              (operationSnapshot.data?.node as any)?.outerRendererA,
            ),
          );
          const outerRendererASnapshot = environment.lookup(
            outerRendererASelector,
          );
          expect(outerRendererASnapshot.isMissingData).toBe(true);
          const outerRendererACallback = jest.fn<[Snapshot], void>();
          environment.subscribe(outerRendererASnapshot, outerRendererACallback);

          next.mockClear();
          dataSource.complete();
          jest.runAllImmediates();
          expect(next).toBeCalledTimes(0);
          expect(error).toBeCalledTimes(0);
          expect(complete).toBeCalledTimes(0);

          resolveFragments[0](markdownRendererNormalizationFragment);
          resolveFragments[1](plaintextRendererNormalizationFragment);
          if (RelayFeatureFlags.BATCH_ASYNC_MODULE_UPDATES_FN != null) {
            jest.runAllImmediates();
            subscription.unsubscribe();
            jest.runAllTimers();
          } else {
            subscription.unsubscribe();
            jest.runAllImmediates();
          }
          expect(outerRendererACallback).toBeCalledTimes(0);
        });
      });
    },
  );
}

runWithFeatureFlags(flags => {
  flags.BATCH_ASYNC_MODULE_UPDATES_FN = null;
});

runWithFeatureFlags(flags => {
  flags.BATCH_ASYNC_MODULE_UPDATES_FN = task => {
    const handle = setTimeout(task, 0);
    return {
      dispose: () => clearTimeout(handle),
    };
  };
});
